package com.xyz.update.update

import android.media.MediaScannerConnection
import android.os.Build
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.blankj.utilcode.util.ActivityUtils
import com.blankj.utilcode.util.FileUtils
import com.blankj.utilcode.util.NetworkUtils
import com.blankj.utilcode.util.Utils
import com.xyz.update.BuildConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.io.RandomAccessFile
import java.net.HttpURLConnection
import java.net.URL
import java.util.Timer
import java.util.TimerTask
import java.util.concurrent.Callable
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicLong


/**
 * 下载管理类、支持多线程
 * 1、检查下载地址否需要重定向
 * 2、地址是否支持分段下载（支持走多线程下载、不支持走单线程下载）
 * 无断网重连、无错误多次尝试
 */
class DownloadKtx {
}

private const val TAG = "DownloadKtx"
private const val BUFFER_SIZE = 8192 // 8KB 缓冲区
private const val DEFAULT_THREAD_COUNT = 5 // 5个下载线程
private var globalBytesRead: AtomicLong = AtomicLong(0)//线程安全Long类型
private var timer: Timer? = null
private val myHandler = Handler(Looper.getMainLooper())
private const val maxRetry = 2
private var currentRetry = 0

/**
 * 1、先检查是否需要重定向
 * 2、检查是否支持多线程下载文件
 */
private fun checkSupportMultiThreading(
    downUrl: String, callBack: (newUrl: String, isSupportMulti: Boolean, fileSize: Long) -> Unit
) {
    val mainScope = MainScope()
    mainScope.launch(Dispatchers.IO) {
        var connection: HttpURLConnection? = null
        try {
            connection = URL(downUrl).openConnection() as HttpURLConnection?
            connection?.instanceFollowRedirects = false
            connection?.requestMethod = "HEAD"
            connection?.connectTimeout = 5000
            connection?.readTimeout = 5000
            connection?.useCaches = true
            connection?.connect()

            if (connection?.responseCode == HttpURLConnection.HTTP_MOVED_PERM
                || connection?.responseCode == HttpURLConnection.HTTP_MOVED_TEMP
            ) {//重定向
                val newUrl = URL(connection.getHeaderField("Location"))
                connection.disconnect()
                mainScope.cancel()
                checkSupportMultiThreading(newUrl.toString(), callBack)
            } else {
                // 获取文件大小
                val fileSize = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    connection?.contentLengthLong ?: connection?.contentLength?.toLong() ?: -1L
                } else {
                    connection?.contentLength?.toLong() ?: -1L
                }
                val acceptRanges = connection?.getHeaderField("Accept-Ranges")
                if ("bytes" == acceptRanges) {
                    withContext(Dispatchers.Main) {
                        currentRetry = 0
                        callBack.invoke(downUrl, true, fileSize)
                    }
                } else {
                    withContext(Dispatchers.Main) {
                        if (currentRetry < maxRetry) {
                            currentRetry += 1
                            try {
                                ActivityUtils.getTopActivity().window.decorView.postDelayed({
                                    checkSupportMultiThreading(downUrl, callBack)
                                }, 100)
                            } catch (e: Exception) {
                                currentRetry = 0
                                callBack.invoke(downUrl, false, fileSize)
                            }
                        } else {
                            currentRetry = 0
                            callBack.invoke(downUrl, false, fileSize)
                        }
                    }
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
            withContext(Dispatchers.Main) {
                if (currentRetry < maxRetry) {
                    currentRetry += 1
                    try {
                        ActivityUtils.getTopActivity().window.decorView.postDelayed({
                            checkSupportMultiThreading(downUrl, callBack)
                        }, 100)
                    } catch (e: Exception) {
                        currentRetry = 0
                        callBack.invoke(downUrl, false, -1L)
                    }
                } else {
                    currentRetry = 0
                    callBack.invoke(downUrl, false, -1L)
                }
            }
        } finally {
            connection?.disconnect()
            mainScope.cancel()
        }
    }
}

/**
 * 保存文件
 * @param downUrl 下载地址
 * @param path 文件地址
 * @param listener 下载进度监听
 */
fun downloadFile(downUrl: String, path: String, listener: DownloadFileListener) {
    globalBytesRead.set(0)
    checkSupportMultiThreading(downUrl) { newUrl, isSupportMulti, fileSize ->
        var file = File(path)
        var isNeedDownload = true
        val pair = checkFileExit(file, path)
        file = pair.first
        if (pair.second) {//文件存在时
            val localSize = FileUtils.getFileLength(file.absolutePath)
            if (localSize == fileSize) {//当前这个包已经下载完成，返回成功
                listener.start()
                listener.success(file.absolutePath)
                listener.end()
                isNeedDownload = false
            } else {//文件存在，但是文件不完整
                file.delete()
                refreshFile(file)
            }
        }
        if (BuildConfig.DEBUG) Log.d(
            TAG, "download apk is multiDownload ${isSupportMulti && fileSize > 0L}"
        )
        if (isNeedDownload) {
            clearOldCacheFile(file, path)
            val newFile = createFile(file)
            if (isSupportMulti && fileSize > 0L) {//多线程下载
                if (timer == null) timer = Timer()
                multiDownload(fileSize, newUrl, newFile, listener)
            } else {//单线程下载
                singleDownload(newUrl, newFile, listener)
            }
        }
    }
}

//多线程下载
private fun multiDownload(
    fileSize: Long, downUrl: String, destinationFile: File, listener: DownloadFileListener
) {
    listener.start()
    val threadCount =
        if (fileSize < 500) 1 else if (fileSize > 1024 * 1024 * 100) DEFAULT_THREAD_COUNT * 2 else DEFAULT_THREAD_COUNT
    val tempFiles: MutableList<File> = ArrayList()//缓存文件
    val executor = Executors.newFixedThreadPool(threadCount)//创建线程池
    try {
        listener.connected()
        val partSize: Long = fileSize / threadCount
        for (i in 0 until threadCount) {
            val start = i * partSize
            val end = if (i == threadCount - 1) fileSize - 1 else (i + 1) * partSize - 1
            val tempFile = File.createTempFile("part$i", ".tmp", destinationFile.parentFile)
            if (BuildConfig.DEBUG) Log.d(TAG, "create tempFile: $tempFile")
            tempFiles.add(tempFile)
            executor.submit(DownloadTask(downUrl, tempFile, start, end, fileSize, listener))
        }
        timer?.schedule(object : TimerTask() {
            override fun run() {
                if (!NetworkUtils.isConnected()) {
                    cancelTime()
                    executor.shutdownNow()
                    myHandler.post {
                        listener.error("网络断开，下载失败！")
                        if (BuildConfig.DEBUG) Log.d(TAG, "下载失败:" + destinationFile.name)
                        listener.end()
                        myHandler.removeCallbacksAndMessages(null)
                    }
                    return
                }
                if (globalBytesRead.get() > 0) {
                    if (BuildConfig.DEBUG) Log.d(
                        TAG,
                        "file download: ${globalBytesRead.get()} of $fileSize"
                    )
                    //计算当前下载百分比，并经由回调传出
                    val progress = (100 * globalBytesRead.get() / fileSize).toInt()
                    if (progress >= 100) {//下载完成
                        listener.progress(100, fileSize)
                        cancelTime()
                        executor.shutdown()
                        mergeFiles(tempFiles, destinationFile) {
                            for (tempFile in tempFiles) {
                                tempFile.delete() // 删除临时文件
                                refreshFile(tempFile)
                            }
                            myHandler.post {
                                if (FileUtils.getFileLength(destinationFile.absolutePath) == fileSize) {
                                    listener.success(destinationFile.absolutePath)
                                } else {
                                    listener.error("下载失败")
                                }
                                listener.end()
                                myHandler.removeCallbacksAndMessages(null)
                            }
                        }
                    } else {
                        myHandler.post {
                            listener.progress(progress, fileSize)
                            myHandler.removeCallbacksAndMessages(null)
                        }
                    }
                }
            }
        }, 0, 150)
    } catch (e: Exception) {
        e.printStackTrace()
        cancelTime()
        executor.shutdownNow()
        listener.error(e.message.toString())
        if (BuildConfig.DEBUG) Log.d(TAG, "下载失败:" + destinationFile.name)
        listener.end()
    } finally {

    }
}

//单线程下载
private fun singleDownload(downUrl: String, file: File, listener: DownloadFileListener) {
    val mainScope = MainScope()
    mainScope.launch(Dispatchers.IO) {
        withContext(Dispatchers.Main) { listener.start() }
        var connection: HttpURLConnection? = null
        try {
            connection = URL(downUrl).openConnection() as HttpURLConnection?
            if (connection != null) {
                connection.instanceFollowRedirects = true
                connection.requestMethod = "GET"
                connection.connectTimeout = 8000
                connection.readTimeout = 5000
                connection.useCaches = false

                if (connection.responseCode == HttpURLConnection.HTTP_OK
                    || connection.responseCode == HttpURLConnection.HTTP_PARTIAL
                ) {
                    var fileSize = connection.contentLength.toLong()
                    withContext(Dispatchers.Main) { listener.connected() }
                    if (fileSize < 0) {
                        if (BuildConfig.DEBUG) Log.d(TAG, "无法获取文件大小")
                        fileSize = 1024
                    }
                    var sum: Long = 0
                    val `in` = connection.inputStream
                    val out = FileOutputStream(file)
                    val buf = ByteArray(BUFFER_SIZE)
                    try {
                        while (true) {
                            val read = `in`.read(buf)
                            if (read == -1) {
                                break
                            }
                            if (!NetworkUtils.isConnected()) {
                                withContext(Dispatchers.Main) {
                                    listener.error("网络断开，下载失败！")
                                    if (BuildConfig.DEBUG) Log.d(TAG, "下载失败:" + file.name)
                                    listener.end()
                                }
                                break
                            }
                            out.write(buf, 0, read)
                            sum += read.toLong()

                            //计算当前下载百分比，并经由回调传出
                            withContext(Dispatchers.Main) {
                                val progress = (100 * sum / fileSize).toInt()
                                listener.progress(progress, fileSize)
                            }
                            if (BuildConfig.DEBUG) Log.d(TAG, "file download: $sum of $fileSize")
                        }
                        if (BuildConfig.DEBUG) Log.d(TAG, "下载成功:" + file.name)
                        withContext(Dispatchers.Main) { listener.success(file.absolutePath) }
                    } catch (e: Exception) {
                        e.printStackTrace()
                        if (BuildConfig.DEBUG) Log.d(TAG, "下载失败:" + file.name)
                        withContext(Dispatchers.Main) { listener.error(e.message.toString()) }
                    } finally {
                        out.flush()
                        out.close()
                        `in`.close()
                    }
                } else {
                    if (BuildConfig.DEBUG) Log.d(TAG, "服务器错误：${connection.responseCode}")
                    withContext(Dispatchers.Main) {
                        listener.error("服务器错误：${connection.responseCode}")
                    }
                }
            } else {
                if (BuildConfig.DEBUG) Log.d(TAG, "服务器连接错误")
                withContext(Dispatchers.Main) {
                    listener.error("服务器连接错误")
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
            if (BuildConfig.DEBUG) Log.d(TAG, "下载失败:" + file.name)
            withContext(Dispatchers.Main) { listener.error(e.message.toString()) }
        } finally {
            connection?.disconnect()
            withContext(Dispatchers.Main) { listener.end() }
            mainScope.cancel()
        }
    }
}

private class DownloadTask(
    private val fileUrl: String,
    private val tempFile: File,
    private val startPos: Long,
    private val endPos: Long,
    private val totalSize: Long,
    private val listener: DownloadFileListener
) : Callable<Void> {

    @Throws(java.lang.Exception::class)
    override fun call(): Void? {
        if (BuildConfig.DEBUG) Log.d(TAG, "DownloadTask: " + Thread.currentThread().name)
        val url = URL(fileUrl)
        val connection = url.openConnection() as HttpURLConnection
        connection.setRequestProperty("Range", "bytes=$startPos-$endPos")
        try {
            connection.inputStream.use { inputStream ->
                FileOutputStream(tempFile).use { outputStream ->
                    val buffer = ByteArray(BUFFER_SIZE)
                    /*var bytesRead: Int
                    while (inputStream.read(buffer).also { bytesRead = it } != -1) {
                        outputStream.write(buffer, 0, bytesRead)
                    }*/
                    while (true) {
                        val read = inputStream.read(buffer)
                        if (read == -1) {
                            break
                        }
                        outputStream.write(buffer, 0, read)
                        val sum = globalBytesRead.addAndGet(read.toLong())
//                        Log.d(TAG, "file download: $sum of $totalSize")
                        //这里回调进度太耗时了，方法外面回调进度
                    }
                    outputStream.flush()
                    outputStream.close()
                }
                inputStream.close()
            }
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            connection.disconnect()
        }
        return null
    }
}

private fun mergeFiles(parts: List<File>, destination: File, callBack: () -> Unit) {
    RandomAccessFile(destination, "rw").use { output ->
        for (part in parts) {
            FileInputStream(part).use { input ->
                val buffer = ByteArray(BUFFER_SIZE)
                var bytesRead: Int
                while (input.read(buffer).also { bytesRead = it } != -1) {
                    output.write(buffer, 0, bytesRead)
                }
                input.close()
            }
        }
        output.close()
        callBack.invoke()
    }
}

private fun cancelTime() {
    timer?.cancel()
    timer = null
}

private fun clearOldCacheFile(destinationFile: File, oldPath: String) {
    destinationFile.parentFile?.listFiles()?.let {
        if (it.isNotEmpty()) {//删除无用的缓存
            val oldFileName = File(oldPath).name//这里只取名字，为了删除之前无效的数据
            for (file in it) {
                val filename = file.name
                if (filename.startsWith("part") && filename.endsWith(".tmp")) {
                    file.delete()
                } else if (filename.contains(oldFileName)) {
                    file.delete()
                }
                refreshFile(file)
            }
        }
    }
}

//创建文件
private fun createFile(file: File): File {
    val parentDir = file.parentFile
    if (parentDir == null || !parentDir.exists()) {
        parentDir?.mkdirs()
    }
    refreshFile(file)
    // 尝试创建新文件
    try {
        if (file.createNewFile()) {
            println("New file created successfully.")
            return file
        } else {
            println("Failed to create a new file.")
            val f = File(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "apk/${file.name}")
            if (f.parentFile?.exists() == false) {
                f.parentFile?.mkdirs()
            }
            f.createNewFile()
            return f
        }
    } catch (e: Exception) {
        try {
            val f = File(Utils.getApp().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "apk/${file.name}")
            if (f.parentFile?.exists() == false) {
                f.parentFile?.mkdirs()
            }
            f.createNewFile()
            return f
        } catch (e: Exception) {
            e.printStackTrace()
            return file
        }
    }
}

private fun refreshFile(file: File) {
    try {
        // 刷新媒体库（适用于外部存储）
        MediaScannerConnection.scanFile(
            ActivityUtils.getTopActivity(),
            arrayOf<String>(file.absolutePath),
            null,
            null
        )
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

private fun checkFileExit(file: File, path: String): Pair<File, Boolean> {
    if (file.exists()) return Pair(file, true)
    val files = file.parentFile?.listFiles()
    if (files.isNullOrEmpty()) return Pair(file, false)
    val oldFileName = File(path).name//这里只取名字，为了删除之前无效的数据
    var hasFile = Pair(file, false)
    for (file2 in files) {
        val filename = file2.name
        if (filename.contains(oldFileName)) {
            hasFile = Pair(file2, true)
            break
        }
    }
    return hasFile
}
